/*************************************************************
 *Name:           Christopher Alexander
 *Email address:  alexanc2@onid.oregonstate.edu
 *Class name:     CS362-400
 *Assignment:     Assignment #5
 *Notes:          This program accepts the following command line
 *                arguments (but does not require them to run):
 *                -g number of times to play dominion
 *                -p number of players (2-4)
 *                -s seed number
 *************************************************************/

#include "dominion.h"
#include "interface.h"
#include <stdio.h>
#include "rngs.h"
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

#define SEED_MIN 0
#define SEED_MAX 1000
#define GAMES_MIN 1
#define GAMES_MAX 1000
#define PLAYERS_MIN 2
#define PLAYERS_MAX 4
#define NUM_CARD_TYPES 27
#define NUM_KCARD_TYPES 20

/*Function Prototypes*/
void parseArgv(int, char**);
int* setRandomGameInitializers(void);
int* setKingdomCards(void);
void playDominion(void);
void playACard(struct gameState *state);
void buyACard(struct gameState *state);
void handleAlarm(int);


//Variables that can either be defined by the user, or will be randomly set.
int numGames = -1;
int numPlayers = -1;
int numSeed = -1;



/*Parse the command line looking for specified options
 *Params: argc, number of arguments.
 *        argv, array of arguments.
 *Return: None.
 */
void parseArgv(int argc, char **argv){
   char opt;

   /*Parse command line looking for option -n.*/
   while ((opt = getopt(argc, argv, "g:p:s:")) != -1){
      switch (opt){
         case 'g':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numGames = strtol(optarg, NULL, 0);
            if (numGames < GAMES_MIN || numGames > GAMES_MAX){
               fprintf(stderr, "Option -g requires an integer argument in the range "
                      "of %d-%d.\n", GAMES_MIN, GAMES_MAX);
               exit(EXIT_FAILURE);
            }
            break;

         case 'p':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numPlayers = strtol(optarg, NULL, 0);
            if (numPlayers < PLAYERS_MIN || numPlayers > PLAYERS_MAX){
               fprintf(stderr, "Option -p requires an integer argument in the range "
                      "of %d-%d.\n", PLAYERS_MIN, PLAYERS_MAX);
               exit(EXIT_FAILURE);
            }
            break;

         case 's':
            /*optarg must be an int between SEED_MIN and SEED_MAX.*/
            numSeed = strtol(optarg, NULL, 0);
            if (numSeed < SEED_MIN || numSeed > SEED_MAX){
               fprintf(stderr, "Option -s requires an integer argument in the range "
                      "of %d-%d.\n", SEED_MIN, SEED_MAX);
               exit(EXIT_FAILURE);
            }
            break;
      }
   }
}


/*If command line args have not been used to set initial game conditions,
 *this function will randomly set those variables.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setRandomGameInitializers() {
   int *kingCards;

   //Set the number of players to be between 2-4
   if (numPlayers < PLAYERS_MIN ) {
      numPlayers = (rand() % 3) + 2;
   }

   //Set the seed to use in initializeGame.
   if (numSeed < SEED_MIN) {
      numSeed = rand() % (SEED_MAX + 1);
   }

   kingCards = setKingdomCards();
   return kingCards;
}


/*Randomly choose 10 kingdom cards to play a game with.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setKingdomCards() {
   int i;
   int usedCards[NUM_CARD_TYPES]; //Will keep track of cards that have been assigned.
   int* k = malloc(10 * sizeof(int));

   for(i = 0; i < 10; i++) {
      //Assign a kingdom card to this position in array.  The kingdom cards are in postions
      //7-26 in enum CARD (see dominion.h)
      k[i] = (rand() % NUM_KCARD_TYPES) + 7;

      //If the card is a duplicate, reassign until a unique card is chosen.
      //Also additional conditions can be placed here to limit the cards used
      //in this tester.  For example, here we are not testing feast.
      while ( usedCards[ (k[i]) ] == -1
             || k[i] == feast
             || k[i] == treasure_map
             || k[i] == sea_hag) {
         k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      }
      //This kingdom card has now been chosen and must not be chosen again.
      usedCards[ (k[i]) ] = -1;
   }
   return k;
}

/*Call playCard() with random cards from hand and values for choices until
 *the call to playCard() is successful.
 *Params: state, a pointer to a struct gameState.
 *Return: None.
 */
void playACard(struct gameState *state) {
   struct gameState testState;
   int handPosToPlay;
   int choice1, choice2, choice3;
   int playCardReturn = -1;
   char cardName[20];

   //Copy the gamestate so that we can test for a valid move before
   //performing that move on our actual gamestate.
   memcpy(&testState, state, sizeof(struct gameState));

   //Continue to try to play a card until a valid play is made.
   while (playCardReturn == -1) {


      //Randomly choose which card in the player's hand to play.
      handPosToPlay = rand() % numHandCards(&testState);
      //Randomly assign valid amounts to the choices for that card.
      choice1 = rand() % NUM_CARD_TYPES;
      choice2 = rand() % NUM_CARD_TYPES;
      choice3 = rand() % NUM_CARD_TYPES;

      //Try to play a card.
      fprintf(stdout, "\t\tPlayer %d TRIES playCard() with parameters:\n", (testState.whoseTurn + 1));
      fprintf(stdout, "\t\t\thandPos = %d\n", handPosToPlay);
      fprintf(stdout, "\t\t\tchoice1 = %d\n", choice1);
      fprintf(stdout, "\t\t\tchoice2 = %d\n", choice2);
      fprintf(stdout, "\t\t\tchoice3 = %d\n", choice3);
      cardNumToName((testState.hand[whoseTurn(state)][handPosToPlay]), cardName);
      fprintf(stdout, "\t\tThe card TRIED was a %s\n", cardName);
      playCardReturn = playCard(handPosToPlay, choice1, choice2, choice3, &testState);

      //If the the card wasn't played correctly. Reset the gamestate and try again.
      if (playCardReturn == -1) {
         memcpy(&testState, state, sizeof(struct gameState));
      }
   }
   //We know we can play the card "for real" now, but should we be allowed to do so?
   //Lets test a few things about the gamestate to find out.
   if(state->hand[whoseTurn(state)][handPosToPlay] == gardens) {
      fprintf(stderr, "Bug found: gardens should not be allowed to be played by playCard().\n");
   }
   if(state->hand[whoseTurn(state)][handPosToPlay] == village) {
      if (testState.numActions != (state->numActions + 1)) {
         fprintf(stderr, "Bug found: village should give +2 actions, but it does not.\n");
      }
   }
   if(state->hand[whoseTurn(state)][handPosToPlay] == smithy) {
      if (testState.handCount[whoseTurn(&testState)] != state->handCount[whoseTurn(state)] + 2) {
         fprintf(stderr, "Bug found: smithy should give +3 cards, but it does not.\n");
      }
   }

   //Play the card "for real"
   fprintf(stdout, "\t\tPlayer %d calls playCard() with parameters:\n", (state->whoseTurn + 1));
   fprintf(stdout, "\t\t\thandPos = %d\n", handPosToPlay);
   fprintf(stdout, "\t\t\tchoice1 = %d\n", choice1);
   fprintf(stdout, "\t\t\tchoice2 = %d\n", choice2);
   fprintf(stdout, "\t\t\tchoice3 = %d\n", choice3);
   cardNumToName((state->hand[whoseTurn(state)][handPosToPlay]), cardName);
   fprintf(stdout, "\t\tThe card PLAYED was a %s\n", cardName);
   playCard(handPosToPlay, choice1, choice2, choice3, state);
}


/*Call buyCard() with random supply piles until
 *the call to buyCard() is successful.
 *Params: state, a pointer to a struct gameState.
 *Return: None.
 */
void buyACard(struct gameState *state) {
   struct gameState testState;
   int cardToBuy;
   int buyCardReturn = -1;
   char cardName[20];

   //Copy the gamestate so that we can test for a valid move before
   //performing that move on our actual gamestate.
   memcpy(&testState, state, sizeof(struct gameState));

   //Continue to try to buy a card until a valid buy is made.
   while (buyCardReturn == -1) {

      //Randomly choose which type of card to buy.
      cardToBuy = rand() % NUM_CARD_TYPES;

      //Try to buy a card.
      fprintf(stdout, "\t\tPlayer %d TRIES buyCard() with parameters:\n", (state->whoseTurn + 1));
      fprintf(stdout, "\t\t\tsupplyPos = %d\n", cardToBuy);
      cardNumToName(cardToBuy, cardName);
      fprintf(stdout, "\t\tThe card tried to buy was a %s\n", cardName);
      buyCardReturn = buyCard(cardToBuy, &testState);

      //If the the card wasn't played correctly. Reset the gamestate and try again.
      if (buyCardReturn == -1) {
         memcpy(&testState, state, sizeof(struct gameState));
      }
   }

   //Buy the card "for real"
   fprintf(stdout, "\t\tPlayer %d CALLS buyCard() with parameters:\n", (state->whoseTurn + 1));
   fprintf(stdout, "\t\t\tsupplyPos = %d\n", cardToBuy);
   cardNumToName(cardToBuy, cardName);
   fprintf(stdout, "\t\tThe card bought was a %s\n", cardName);
   buyCard(cardToBuy, state);

}

/*Plays a complete game of dominion.  Players randomly decide whether or
 *not to play cards and buy cards when it is their turn.
 *Params: None.
 *Return: None.
 */
void playDominion() {

   int *kingCards;
   struct gameState state;
   int i, j;
   char cardName[20];
   int yesNo;
   int *whoWins = malloc(sizeof(int) * MAX_PLAYERS);

   kingCards = setRandomGameInitializers();

   //You may enter your own kingdom cards here if you would like to do
   //so for the purpose of repeating a test run:
   //kingCards[0] = sea_hag;
   //kingCards[1] = feast;
   //kingCards[2] = mine;
   //kingCards[3] = steward;
   //kingCards[4] = treasure_map;
   //kingCards[5] = council_room;
   //kingCards[6] = gardens;
   //kingCards[7] = tribute;
   //kingCards[8] = remodel;
   //kingCards[9] = great_hall;


   fprintf(stdout, "The kingdom cards used to initialize this game were:\n");
   for (i = 0; i < 10; i++) {
      cardNumToName(kingCards[i], cardName);
      fprintf(stdout, "\t%s\n", cardName);
   }

   initializeGame(numPlayers, kingCards, numSeed, &state);
   free(kingCards);

   fprintf(stdout, "The number of players in this game = %d\n", numPlayers);
   fprintf(stdout, "The seed number for this game = %d\n", numSeed);

   fprintf(stdout, "\nAfter game initialization, the counts of all card piles are:\n");
   for (i = 0; i < NUM_CARD_TYPES; i++) {
      cardNumToName(i, cardName);
      fprintf(stdout, "\t%s = %d\n", cardName, state.supplyCount[i]);
   }

   fprintf(stdout, "\nThe following is a record of the gameplay for this game:\n\n");


   //Keep playing until the game is over.
   while (!isGameOver(&state)) {

      fprintf(stdout, "\tPLAYER %d TURN\n", (state.whoseTurn + 1));

      //While the player is allowed to play a card.
      while (state.numActions > 0) {
         //Determine if player is going to play a card
         yesNo = rand() % 2;
         if (yesNo == 1) {
            //We are going to play a card but we can only play a card if
            //the player has a card to play in their hand.
            for (j = 0; j < numHandCards(&state); j++){
               if ((handCard(j, &state) >= adventurer) && (handCard(j, &state) <= treasure_map)) {
                  playACard(&state);
                  break; //Only one card in the hand should be played.
               }
            }
         }
         else {
            //Player chooses to not play a card.
            break;
         }
      }

      //While the player is allowed to buy a card.
      while (state.numBuys > 0) {
         //Determine if player is going to buy a card
         yesNo = rand() % 2;
         if (yesNo == 1) {
            //We are going to buy a card
            buyACard(&state);
         }
         else {
            //Player chooses to not buy a card.
            break;
         }
      }
      //fprintf(stderr, "WE ARE NOW OUT OF WHILE LOOP\n\n");

      endTurn(&state);
   }

   getWinners(whoWins, &state);

   //Determine who wins
   fprintf(stdout, "\nTHE WINNER(S) OF THE GAME ARE:\n");
   for (i = 0; i < MAX_PLAYERS; i++) {
      if (whoWins[i] == 1) {
         fprintf(stdout, "\tPlayer %d\n\n", (i+1));
      }
   }
}

/*In cases where the program enters into an infinite loop due to a bug,
 *this function will exit the program.
 *Params: sig, the signal.
 *Return: None.
 */
void handleAlarm(int sig) {
   fprintf(stderr, "\n\nGAME ENTERED AN INFINITE LOOP\n");
   fprintf(stderr, "Check gameResults.out to see the last command attempted.\n\n");
   exit(EXIT_FAILURE);
}


int main (int argc, char** argv) {
   int i;

   srand(time(NULL)); //Set up the RNG that I use (rand)
   PlantSeeds(-1); //Set up the RNG that functions in dominion use (rngs.c)
   signal(SIGALRM, handleAlarm);

   parseArgv(argc, argv);

   //Set the number of games to play.
   if (numGames < GAMES_MIN) {
      numGames = (rand() % GAMES_MAX) + 1;
   }

   fprintf(stdout, "Total number of games played in this test = %d\n", numGames);
   fprintf(stderr, "Total number of games played in this test = %d\n", numGames);

   //Play the games.
   for (i = 0; i < numGames; i++) {
      fprintf(stdout, "\nRESULTS FROM GAME # %d\n\n", (i+1));
      fprintf(stderr, "\nRESULTS FROM GAME # %d\n\n", (i+1));

      //Using alarm to exit the program in the case of it entering an infinite loop.
      alarm(3);
      playDominion();
      alarm(0);
      fprintf(stdout, "**************** END OF GAME %d ******************\n\n", (i+1));
   }

   return 0;
}
